﻿using Levfier.GUI.EditorControls.Chains;
using Levfier.GUI.EditorControls.Exceptions;
using Levfier.GUI.Status;
using Levfier.Proccessors;
using Levfier.Proccessors.Chains;
using Levfier.Proccessors.Parameters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;

namespace Levfier.GUI.EditorControls
{
    /// <summary>
    /// プロセッサに対応するGUIコントロール
    /// </summary>
    [Serializable]
    abstract public class ProccessorControl : EditorControl, IProccessorControl
    {
        #region 定数

        /// <summary>
        /// 選択時の枠色
        /// </summary>
        protected readonly Brush selectedBrush = new SolidColorBrush(Color.FromArgb(0xFF, 0x00, 0xAA, 0xDE));

        #endregion

        #region プロパティ

        /// <summary>
        /// 通過した回数
        /// </summary>
        public int PassedThroughCount { get; set; }

        /// <summary>
        /// その他の関連コントロール
        /// </summary>
        public IList<IDrawable> DependencyControls { get { return dependencyControls; } set { dependencyControls = value; } }

        /// <summary>
        /// その他の関連コントロール
        /// </summary>
        protected IList<IDrawable> dependencyControls;

        /// <summary>
        /// 上流のチェインコントロール
        /// </summary>
        protected IList<IChainControl> upperChainControls;

        /// <summary>
        /// 下流のチェインコントロール
        /// </summary>
        protected IList<IChainControl> lowerChainControls;

        /// <summary>
        /// プロセッサ
        /// </summary>
        protected IProccessable proccessor;

        /// <summary>
        /// 上流チェインの接続最大数
        /// 通常は1
        /// </summary>
        protected int upperChainConnectionMaxNum = 1;

        #endregion

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public ProccessorControl()
        {
            this.Position = new Point(0, 0);
            this.upperChainControls = new List<IChainControl>();
            this.lowerChainControls = new List<IChainControl>();
            this.DependencyControls = new List<IDrawable>();
            proccessor = null;
        }

        /// <summary>
        /// 座標を更新する
        /// </summary>
        public override void Update()
        {
            base.Update();

            //上流・下流の接続先を描画更新する
            foreach (IDrawable drawer in this.upperChainControls)
            {
                drawer.Update();
            }

            foreach (IDrawable drawer in this.lowerChainControls)
            {
                drawer.Update();
            }

            foreach (IDrawable drawer in this.DependencyControls)
            {
                drawer.Update();
            }
        }

        /// <summary>
        /// プロセッサを取得させる
        /// </summary>
        /// <returns>プロセッサ</returns>
        public IProccessable GetProccessor()
        {
            return this.proccessor;
        }

        /// <summary>
        /// ルートをチェックする
        /// </summary>
        /// <exception cref="RouteLoopException">ループが発生した場合に発生する</exception>
        /// <returns>出力まで到達できなかった末端経路数</returns>
        public virtual int CheckRoute()
        {
            //通過回数をインクリメント
            this.PassedThroughCount++;

            //通過した回数が上流チェインの数より多い場合、ループと判定
            //ただし、上流チェインが0の場合、例外を吐かない
            if (0 != upperChainControls.Count && upperChainControls.Count < this.PassedThroughCount)
            {
                //画面でキャッチすること
                throw new RouteLoopException("接続の循環が発生しました。", null);
            }

            //末端の場合（出力に到達していない場合）
            if (0 == this.lowerChainControls.Count)
            {
                return 1;
            }

            //下流で出力に到達していない経路数を合計する
            int sum = 0;
            foreach (IChainControl chainControl in this.lowerChainControls)
            {
                sum += chainControl.CheckRoute();
            }

            return sum;
        }

        /// <summary>
        /// ルートチェックを初期化する
        /// </summary>
        public void InitRouteCheck()
        {
            foreach (IChainControl chainControl in this.lowerChainControls)
            {
                chainControl.InitRouteCheck();
            }
            this.PassedThroughCount = 0;
        }


        /// <summary>
        /// 上流チェインコントロールを接続する
        /// </summary>
        /// <param name="chainControl">接続対象コントロール</param>
        public virtual void SetUpperChainControl(IChainControl chainControl)
        {
            //接続最大値を超えていない場合
            if(this.upperChainControls.Count < this.upperChainConnectionMaxNum)
            {
                this.upperChainControls.Add(chainControl);

                //上流のチェインにプロセッサを接続する
                IChainable chain = chainControl.GetChain();
                chain.SetProccessor(this.proccessor);
            }
            else //最大値を超えている場合
            {
                throw new ChainFailException("接続上限に達しました。", null);
            }
        }

        /// <summary>
        /// 上流チェインコントロールを削除する
        /// </summary>
        /// <param name="chainControl">削除対象コントロール</param>
        public virtual void RemoveUpperChainControl(IChainControl chainControl)
        {
            this.upperChainControls.Remove(chainControl);

            //上流のチェインからプロセッサを削除する
            IChainable chain = chainControl.GetChain();
            chain.RemoveProccessor(this.proccessor);
        }

        /// <summary>
        /// 下流チェインコントロールを接続する
        /// </summary>
        /// <param name="chainControl">接続対象コントロール</param>
        public virtual void SetLowerChainControl(IChainControl chainControl)
        {
            //下流のチェイン
            IChainable chain;

            //すでに下流のチェインが接続されている場合
            if(this.lowerChainControls.Count > 0)
            {
                //すでにあるチェインを使用する
                chain = this.lowerChainControls[0].GetChain();
                chainControl.SetChain(chain);
            }
            else
            {
                //後段のチェインを接続する
                chain = chainControl.GetChain();
                this.proccessor.SetLowerChain(chain);
            }
            this.lowerChainControls.Add(chainControl);
        }

        /// <summary>
        /// 下流チェインコントロールを削除する
        /// </summary>
        /// <param name="chainControl">削除対象コントロール</param>
        public virtual void RemoveLowerChainControl(IChainControl chainControl)
        {
            if(null != chainControl)
            {
                this.lowerChainControls.Remove(chainControl);

                //もうほかのチェインが接続されていない場合
                if(this.lowerChainControls.Count == 0)
                {
                    //チェインを削除する
                    this.proccessor.RemoveLowerChain();
                }
            }
        }

        /// <summary>
        /// 選択された時の動作
        /// 末端の子クラスで実装すること
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        abstract public override void Select(object sender);

        /// <summary>
        /// 選択解除された時の動作
        /// 末端の子クラスで実装すること
        /// </summary>
        /// <param name="sender">イベント発生源</param>
        abstract public override void Unselect(object sender);

        /// <summary>
        /// プロセッサのパラメータを設定するエディター画面を取得する
        /// </summary>
        /// <returns>プロセッサのパラメータを設定するエディター画面</returns>
        public override System.Windows.Controls.UserControl GetPropertyEditorView()
        {
            IProccessable proccessor = this.GetProccessor();
            if (null != proccessor && 0 != proccessor.GetParameters().Count)
            {
                return new ProccessorPropertyEditorView(proccessor.GetParameters());
            }
            return null;   
        }

        /// <summary>
        /// 自分自身を削除する
        /// </summary>
        public override IEnumerable<UIElement> Delete()
        {
            //リストの削除を途中で行うと、InvalidOperationExceptionが発生するため
            //別のリストにまとめて操作を行う
            List<IDrawable> deleteTargets = new List<IDrawable>();
            deleteTargets.AddRange(this.upperChainControls);
            deleteTargets.AddRange(this.lowerChainControls);
            deleteTargets.AddRange(this.DependencyControls);

            //上流を削除
            foreach (IDrawable chain in deleteTargets)
            {
                chain.Delete();
            }
            deleteTargets.Add(this);
            this.proccessor = null;

            return deleteTargets.Cast<UIElement>();
        }

        /// <summary>
        /// Statsを生成する
        /// </summary>
        /// <returns>Status</returns>
        public override ControlStatus GetStatus()
        {
            EditorControlStatus status = base.GetStatus() as EditorControlStatus;

            if (null != this.proccessor)
            {
                //パラメータを取り出し、追加する。
                foreach (ProccessParameter param in this.proccessor.GetParameters().Values)
                {
                    ParameterStatus paramStatus = new ParameterStatus();
                    paramStatus.Value = param.Value;
                    paramStatus.Name = param.Name;

                    //以前作ったバージョンがnullなので、バグる
                    if (null != param.Id)
                    {
                        paramStatus.Id = param.Id;
                    }
                    status.ParameterStatuses.Add(paramStatus);
                }
            }

            return status;
        }

        /// <summary>
        /// パラメータステータスをロードする
        /// </summary>
        /// <param name="paramStatuses">パラメータステータスのコレクション</param>
        /// <exception cref="NotFoundParameterStatusException">パラメータが見つからない場合に発生する</exception>
        public IEnumerable<ProccessParameter> LoadParameterStatus(IEnumerable<ParameterStatus> paramStatuses)
        {
            bool error = false;
            string errorPropertyStr = "";
            List<ProccessParameter> paramList = new List<ProccessParameter>();

            foreach(ParameterStatus status in paramStatuses)
            {
                if (this.proccessor.GetParameters().ContainsKey(status.Name))
                {
                    ProccessParameter param = this.proccessor.GetParameters()[status.Name];
                    param.Value = status.Value;

                    //以前作ったバージョンがnullなので、バグる
                    if (null != status.Id)
                    {
                        param.Id = status.Id;
                    }
                    paramList.Add(param);
                }
                else
                {
                    error = true;
                    errorPropertyStr += "[" + status.Name + "]\n";
                }
            }

            if(error)
            {
                throw new NotFoundParameterStatusException("パラメータ\n" + errorPropertyStr + "が見つかりません。", null);
            }

            return paramList;
        }
    }    
}